/*!
 * sense8--第8感js 前端框架
 * 集成path和fxtpl
 *
 * Copyright 2017, 
 * Released under the MIT license
 * Version: 1.0.2
 * 
 * Date: 2017-02-10
 */
;(function(){
  var Path = {
    'version': "0.8.4",
    'map': function (path) {
        if (Path.routes.defined.hasOwnProperty(path)) {
            return Path.routes.defined[path];
        } else {
            return new Path.core.route(path);
        }
    },
    'root': function (path) {
        Path.routes.root = path;
    },
    'rescue': function (fn) {
        Path.routes.rescue = fn;
    },
    'history': {
        'initial':{}, // Empty container for "Initial Popstate" checking variables.
        'pushState': function(state, title, path){
            if(Path.history.supported){
                if(Path.dispatch(path)){
                    history.pushState(state, title, path);
                }
            } else {
                if(Path.history.fallback){
                    window.location.hash = "#" + path;
                }
            }
        },
        'popState': function(event){
            var initialPop = !Path.history.initial.popped && location.href == Path.history.initial.URL;
            Path.history.initial.popped = true;
            if(initialPop) return;
            Path.dispatch(document.location.pathname);
        },
        'listen': function(fallback){
            Path.history.supported = !!(window.history && window.history.pushState);
            Path.history.fallback  = fallback;

            if(Path.history.supported){
                Path.history.initial.popped = ('state' in window.history), Path.history.initial.URL = location.href;
                window.onpopstate = Path.history.popState;
            } else {
                if(Path.history.fallback){
                    for(route in Path.routes.defined){
                        if(route.charAt(0) != "#"){
                          Path.routes.defined["#"+route] = Path.routes.defined[route];
                          Path.routes.defined["#"+route].path = "#"+route;
                        }
                    }
                    Path.listen();
                }
            }
        }
    },
    'match': function (path, parameterize) {
        var params = {}, route = null, possible_routes, slice, i, j, compare;
        for (route in Path.routes.defined) {
            if (route !== null && route !== undefined) {
                route = Path.routes.defined[route];
                possible_routes = route.partition();
                for (j = 0; j < possible_routes.length; j++) {
                    slice = possible_routes[j];
                    compare = path;
                    if (slice.search(/:/) > 0) {
                        for (i = 0; i < slice.split("/").length; i++) {
                            if ((i < compare.split("/").length) && (slice.split("/")[i].charAt(0) === ":")) {
                                params[slice.split('/')[i].replace(/:/, '')] = compare.split("/")[i];
                                compare = compare.replace(compare.split("/")[i], slice.split("/")[i]);
                            }
                        }
                    }
                    if (slice === compare) {
                        if (parameterize) {
                            route.params = params;
                        }
                        return route;
                    }
                }
            }
        }
        return null;
    },
    'dispatch': function (passed_route) {
        var previous_route, matched_route;
        if (Path.routes.current !== passed_route) {
            Path.routes.previous = Path.routes.current;
            Path.routes.current = passed_route;
            matched_route = Path.match(passed_route, true);

            if (Path.routes.previous) {
                previous_route = Path.match(Path.routes.previous);
                if (previous_route !== null && previous_route.do_exit !== null) {
                    previous_route.do_exit();
                }
            }

            if (matched_route !== null) {
                matched_route.run();
                return true;
            } else {
                if (Path.routes.rescue !== null) {
                    Path.routes.rescue();
                }
            }
        }
    },
    'listen': function () {
        var fn = function(){ Path.dispatch(location.hash); }

        if (location.hash === "") {
            if (Path.routes.root !== null) {
                location.hash = Path.routes.root;
            }
        }

        // The 'document.documentMode' checks below ensure that PathJS fires the right events
        // even in IE "Quirks Mode".
        if ("onhashchange" in window && (!document.documentMode || document.documentMode >= 8)) {
            window.onhashchange = fn;
        } else {
            setInterval(fn, 50);
        }

        if(location.hash !== "") {
            Path.dispatch(location.hash);
        }
    },
    'core': {
        'route': function (path) {
            this.path = path;
            this.action = null;
            this.do_enter = [];
            this.do_exit = null;
            this.params = {};
            Path.routes.defined[path] = this;
        }
    },
    'routes': {
        'current': null,
        'root': null,
        'rescue': null,
        'previous': null,
        'defined': {}
    }
};
Path.core.route.prototype = {
    'to': function (fn) {
        this.action = fn;
        return this;
    },
    'enter': function (fns) {
        if (fns instanceof Array) {
            this.do_enter = this.do_enter.concat(fns);
        } else {
            this.do_enter.push(fns);
        }
        return this;
    },
    'exit': function (fn) {
        this.do_exit = fn;
        return this;
    },
    'partition': function () {
        var parts = [], options = [], re = /\(([^}]+?)\)/g, text, i;
        while (text = re.exec(this.path)) {
            parts.push(text[1]);
        }
        options.push(this.path.split("(")[0]);
        for (i = 0; i < parts.length; i++) {
            options.push(options[options.length - 1] + parts[i]);
        }
        return options;
    },
    'run': function () {
        var halt_execution = false, i, result, previous;

        if (Path.routes.defined[this.path].hasOwnProperty("do_enter")) {
            if (Path.routes.defined[this.path].do_enter.length > 0) {
                for (i = 0; i < Path.routes.defined[this.path].do_enter.length; i++) {
                    result = Path.routes.defined[this.path].do_enter[i].apply(this, null);
                    if (result === false) {
                        halt_execution = true;
                        break;
                    }
                }
            }
        }
        if (!halt_execution) {
            Path.routes.defined[this.path].action();
        }
    }

};
  //输出全局变量 用于调用
  window.Path = Path;
  //Fxtpl命名空间
  var Fxtpl = {
    render : function(id, data, options){//渲染模板
      data = data || this.data;
      var elm = document.getElementById(id), fn, html;
      if(elm){
        if(id in this.cache){
          fn = this.cache[id];
        }else{
          fn = this.cache[id] = this.compile(elm.innerHTML, '', options);
        }
        try{
          html = fn(data);
        }catch(e){
          html = Fxtpl.error(e, '#'+ id, 'Render');
        }
        if(elm.nodeName.toUpperCase() !== 'SCRIPT'){
          html = html.replace(/<[^>]+>/g, function(a){
            return a.replace(/\btpl:/ig,'');//替换tpl虚构标签
          });
          try{
            elm.innerHTML = html;
          }catch(e){// IE6-IE9中某些标签的innerHTML只读而不能设值，如table、tbody、tr等，直接使用jQuery的html方法
            window.$ && $(elm).html(html);
          }
          elm.style.visibility = 'visible';
        }
        return html;
      }else{
        hasConsole && console.warn('Fxtpl cannot find \''+ id +'\'');
      }
    },
    compile : function(str, data, options){//编译字符串
      var rep = this.__proto__?
        ["var s='';", "s+='", "';", "'+(", ")+'", "return s;"]:
        ["var s=[];", "s.push('", "');", "',", ",'", "return s.join('');"];
      var o = options || {};
      var _this = this;
      var _left = o.leftTag || this.config.leftTag;
      var _right = o.rightTag || this.config.rightTag;
      var isSmartyMode = /<!--/.test(_left);//是否类Smarty渲染模式（直接渲染模式）
      if(isSmartyMode){
        var _l = _left.replace('<','&lt;');
        var _r = _right.replace('>','&gt;');
        str = str.split(_l).join(_left).split(_r).join(_right);
      }
      var escape = o.escape || this.config.escape;
      var varString = [];
      var fnBody = rep[0] + rep[1] +
        str
        .replace(/[\r\t\n]/g, "")
        .split(_left).join("\t")
        .split(_right).join("\r")
        .replace(/((^|\r)([^\t]*))/g, function(a,b,c,d){//html处理
          return c + d.replace(/('|\\)/g, '\\$1');
        })
        .replace(/(\t)(.*?)(\r)/g, function(a,b,c){//code处理
          if(isSmartyMode){
            //类Smarty渲染模式需要的代码转义
            c = c.replace(/&amp;/g,'&')
            .replace(/&lt;/g,'<')
            .replace(/&gt;/g,'>');
          }
          return _this.parsing(c, varString, rep, escape);
        }) +
        rep[2] + rep[5];
      fnBody = varString.join('') + fnBody;
      if(o.debug && hasConsole){
        console.log('Template HTML:' + str);
        console.log('Template Render:\nfunction(data){' + fnBody + '}');
      }
      try{
        var fn = new Function("data", fnBody);
        return data? fn(data): fn;
      }catch(e){
        var eMsg = Fxtpl.error(e, str, 'Compile');
        return function(){
          return eMsg;
        };
      }
    },
    parsing : function(code, varString, rep, escape){//编译语法
      var codeArr = code.replace(/^\s*|\s*$/g,'').replace(/\s{2,}/g,' ').split(' ');//处理多余空格后分组
      var varArr = /\$(\w+)/g.exec(code);
      if(varArr && !varString[varArr[1]]){//储存变量
        varString.push('var $',varArr[1],'=data.',varArr[1],';');
        varString[varArr[1]] = true;
      }
      switch(codeArr[0]){
        case 'if':
          return rep[2] + 'if('+ code.slice(3) +'){' + rep[1];
        case 'else':
          return rep[2] + '}else{' + rep[1];
        case 'elseif':
          return rep[2] + '}else if('+ code.slice(7) +'){' + rep[1];
        case '/if':
          return rep[2] + '}' + rep[1];
        case 'each':
          var e_o = codeArr[1];
          var e_p = codeArr[3] || '$item';
          var e_i = (codeArr[2] === 'as'? codeArr[4]: codeArr[2]) || '$index';
          return rep[2] + 'Fxtpl.each('+ e_o +',function('+ e_p +','+ e_i +'){' + rep[1];
        case '/each':
          return rep[2] + '});' + rep[1];
        case 'include':
          return rep[3] + 'Fxtpl.render('+ codeArr.slice(1).join('') +')' + rep[4];
        default://输出变量
          if(/[^|]\|[^|]/.test(code)){//helpers
            code = Fxtpl.filter(code);
          }
          return rep[3] + (escape?'Fxtpl.escape(String('+ code +'))' : code) + rep[4];
      }
    },
    filter: function(code){
      var arr = code.split('|');
      var str = arr[0];
      var len = arr.length;
      for(var i = 1; i < len; i++){
        var fnArr = arr[i].replace(/^\s+/, '').split(' ');
        var fnName = fnArr.shift();
        var args = fnArr.join(' ');
        args = args? ', ' + args : args;
        str = 'Fxtpl.helpers.' + fnName + '(' + str + args + ')';
      }
      return str;
    },
    escape : function(str){//HTML转义
      return str
          .replace(/&/g,'&amp;')
          .replace(/</g,'&lt;')
          .replace(/>/g,'&gt;')
          .replace(/"/g,'&quot;')
          .replace(/'/g,'&#39;');
    },
    each : function(data, callback){//遍历对象
      if (({}).toString.call(data) === '[object Array]') {
          for (var i = 0, len = data.length; i < len; i++) {
              callback.call(data[i], data[i], i);
          }
      } else {
          for (var p in data) {
              callback.call(data[p], data[p], p);
          }
      }
    },
    helpers: {
      escape: function(str){
        return Fxtpl.escape(String(str));
      }
    },
    error: function(e, id, type){
      var pos = id.length > 100? id.slice(0, 100)+ '...': id;
      var eMsg = 'Fxtpl '+ type +' Error: ' + e.message + ' in \'' + pos +'\'';
      hasConsole && console.error(eMsg);
      return eMsg;
    },
    cache : {},//模板函数缓存
    data : {},//模板全局数据缓存
    config: {//模板全局配置
      leftTag : '<!--[',//语法左分隔符
      rightTag : ']-->',//语法左右隔符
      escape : false//模板输出的变量是否HTML转义
    }
  };
  //浏览器调式判断
  var hasConsole = !!window.console;
  //输出全局变量
  window.Fxtpl = Fxtpl;

})();
